/*
 * ============================================================================
 *                   GNU Lesser General Public License
 * ============================================================================
 *
 *
 *
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 *
 *
 *
 */
package rad.framework.seam;

import java.io.OutputStream;
import java.util.Collections;
import java.util.List;

import javax.ejb.Remove;
import javax.persistence.Query;

import rad.framework.jpa.EntityPersistenceContextMap;
import rad.framework.jpa.JpaUtil;
import rad.framework.jsf.JsfUtil;
import rad.framework.richfaces.EntityTreeNode;
import rad.framework.richfaces.RootTreeNode;
import rad.framework.security.jpa.Action;
import rad.framework.security.jpa.PermissionsUtil;
import rad.framework.xml.BetwixtUtil;
import rad.framework.xml.BetwixtUtil.EmptySuppressionStrategy;

import org.apache.commons.lang.StringUtils;
import org.hibernate.validator.ClassValidator;
import org.hibernate.validator.InvalidStateException;
import org.hibernate.validator.InvalidValue;
import org.jboss.seam.Seam;
import org.jboss.seam.annotations.Begin;
import org.jboss.seam.annotations.Destroy;
import org.jboss.seam.annotations.End;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.core.ResourceBundle;
import org.jboss.seam.core.Validators;
import org.jboss.seam.framework.EntityHome;
import org.jboss.seam.international.StatusMessage.Severity;
import org.jboss.seam.persistence.PersistenceProvider;
import org.jboss.seam.security.Identity;

/**
 * The base implementation of the Finder interface.
 *
 * @author boudyacho
 */
@SuppressWarnings("serial")
public abstract class AbstractEditorBean<E> extends EntityHome<E> {

	public AbstractEditorBean() {
	}

	// ------------------------------------

	protected String getEntityClassName() {
		return getEntityClass().getSimpleName();
	}

	protected String getPersistenceContextName() {
		return EntityPersistenceContextMap.instance()
				.getPersistenceContextNameFor(getEntityClass());
	}

	@Out("editorName")
	public String getName() {
		return Seam.getComponentName(getClass());
	}

	protected String getInstanceName() {
		return StringUtils.uncapitalize(getEntityClassName());
	}

	// ------------------------------------
	// entity access
	// ------------------------------------

	public void setRequestedId(String id) {
		setId(convertId(id));
	}

	public String getRequestedId() {
		if (super.getId() != null) {
			return super.getId().toString();
		}
		return null;
	}

	protected Object convertId(String id) {
		return new Long(id);
	}

	public E newInstance() {
		return createInstance();
	}

	// ------------------------------------
	// Render Rules
	// ------------------------------------

	public boolean isRenderCreate() {
		return PermissionsUtil.hasPermissions(getEntityClass(),
				Action.INSERT);
	}

	public boolean isRenderEdit() {
		return isRenderEdit(getInstance());
	}

	public boolean isRenderEdit(E instance) {
		return PermissionsUtil.hasPermissions(instance,
				Action.UPDATE);
	}

	public boolean isRenderDelete() {
		return isManaged() && canDelete();
	}

	public boolean isRenderDelete(E instance) {
		return PermissionsUtil.hasPermissions(instance,
				Action.DELETE);
	}

	protected boolean canDelete() {
		return PermissionsUtil.hasPermissions(getInstance(),
				Action.DELETE);
	}

	public boolean isRenderView() {
		return true;
	}

	public boolean isRenderPrint() {
		return true;
	}

	public boolean isRenderCharts() {
		return false;
	}

	public boolean isRenderCreateClone() {
		return isRenderCreate() && isManaged();
	}

	protected boolean isSimpleEntity() {
		return false;
	}

	public boolean isEndConversation() {
		return isSimpleEntity() && isTop();
	}

	public boolean isViewMode() {
		return !isEditMode();
	}

	public boolean isEditMode() {
		return JsfUtil.getViewId().toLowerCase().contains("edit");
	}

	protected void setViewMode(boolean viewMode) {
		if (getTreeNode() != null) {
			getTreeNode().setViewMode(viewMode);
		}
	}

	// ------------------------------------
	// Validation
	// ------------------------------------

	public String validate() {
		isValid();
		return null;
	}

	protected boolean isValid() {
		PersistenceProvider.instance().setFlushModeManual(getEntityManager()); // TODO needed?
		boolean result = preSaveValidation();
		ClassValidator validator = Validators.instance().getValidator(
				getInstance().getClass());
		InvalidValue[] ivs = validator.getInvalidValues(getInstance());
		if (ivs.length > 0) {
			for (InvalidValue invalidValue : ivs) {
				getStatusMessages().add(Severity.ERROR, invalidValue.getMessage());
			}
			if (isManaged()) {
				getEntityManager().refresh(getInstance());
			}
			result = false;
		}
		return result;
	}

	protected boolean preSaveValidation() {
		return true;
	}

	protected void postSaveValidation() {
	}

	@SuppressWarnings("unchecked")
	public void addValidationFacesMessage(String message, Class beanClass,
			String propertyName, Object value) {
		addValidationFacesMessage(message, beanClass, propertyName, value,
				getInstance());
	}

	@SuppressWarnings("unchecked")
	public void addValidationFacesMessage(String message, Class beanClass,
			String propertyName, Object value, Object bean) {
		InvalidValue inv = new InvalidValue(message, beanClass, propertyName,
				value, bean);
		getStatusMessages().add(inv);
	}

	public void addInfoFacesMessage(String message, Object... params) {
		getStatusMessages().add(Severity.INFO, message, params);
	}

	public void addWarnFacesMessage(String message, Object... params) {
		getStatusMessages().add(Severity.WARN, message, params);
	}

	public void addErrorFacesMessage(String message, Object... params) {
		getStatusMessages().add(Severity.ERROR, message, params);
	}

	// ------------------------------------
	// Persistence
	// ------------------------------------

	public E quickAdd() {
		if (!isValid()) {
			return null;
		}
		prePersist();
		persist();
		flush();
		postSaveValidation();
		E added = getInstance();
		setInstance(null);
		setId(null);
		return added;
	}

	public boolean save(E instance) {
		EntityTreeNode<E> temp = getTreeNode();
		setTreeNode(null);
		setInstance(instance);
		String outcome = save();
		setTreeNode(temp);
		return outcome != null;
	}

	protected void prePersist() {
	}

	@SuppressWarnings("unchecked")
	public String save() {
		if (!isValid()) {
			return null;
		}

		try {
			if (isManaged()) {
				update();
			} else {
				prePersist();
				persist();
			}
			flush();
			postSaveValidation();
			if (isMasterNode()) {
				addRecentItem();
			}
		} catch (InvalidStateException e) {
			getStatusMessages().add(e.getInvalidValues());
			return null;
		}

		if (isSimpleEntity()) {
			return getFindOutcome();
		} else {
			return getViewOutcome();
		}
	}

	@Transactional
	public String persist() {
		joinTransaction();
		return super.persist();
	}

	protected void flush() {
		joinTransaction();
		getEntityManager().flush();
	}


	public boolean delete(E instance) {
		EntityTreeNode<E> temp = getTreeNode();
		setTreeNode(null);
		setInstance(instance);
		String outcome = delete();
		setTreeNode(temp);
		return outcome != null;
	}

	public String delete() {
		if (canDelete()) {
			preRemove();
			joinTransaction();
			super.remove();
			removeRecentItem();
			clear();
			setInstance(createInstance());
			return getFindOutcome();
		} else {
			getStatusMessages().addFromResourceBundleOrDefault(
					Severity.ERROR,
					getEntityClassName() + "_DeleteError",
					"Can not delete due to assocation with other data.");
			return null;
		}
	}

	protected void preRemove() {
	}

	protected int countReferences(String eql) {
		Query qry = getEntityManager().createQuery("select count(*) " + eql).setParameter("me",
				getInstance());
		return ((Long) qry.getSingleResult()).intValue();
	}


	public void clear() {
		setTreeNode(null);
		clearInstance();
	}

	// ------------------------------------
	// Navigation related
	// ------------------------------------

	protected String getEditOutcome() {
		return "edit" + getEntityClassName();
	}

	protected String getViewOutcome() {
		return "view" + getEntityClassName();
	}

	protected String getFindOutcome() {
		return "find" + getEntityClassName();
	}

	public String cancelEdit() {
		clearInstance();
		if (getTreeNode() != null) {
			getTreeNode().setInstance(null);
		}
		return getFindOutcome();
	}

	@End
	public String cancelEditAndEnd() {
		return cancelEdit();
	}

	@End
	public String saveAndEnd() {
		String outcome = save();
		clear();
		return outcome;
	}

	@End
	public String deleteAndEnd() {
		return delete();
	}

	@Begin
	@End
	public String deleteBeginAndEnd() {
		return delete();
	}

	// ------------------------------------
	// lifecycle related
	// ------------------------------------

	@Destroy
	@Remove
	@BypassInterceptors
	public void destroy() {
		debug("Destroy: #0 : #1", getId(), this);
	}

	// ------------------------------------
	// User Identity
	// ------------------------------------

	public String getCurrentUserName() {
		Identity identity = Identity.instance();
		if (identity.isLoggedIn()) {
			return identity.getCredentials().getUsername();
		} else {
			throw new SecurityException(ResourceBundle.instance().getString("NotLoggedIn"));
		}
	}

	protected boolean isLoggedIn() {
		Identity identity = Identity.instance();
		return identity.isLoggedIn();
	}

	protected boolean isUserInRole(String role) {
		Identity identity = Identity.instance();
		return identity.hasRole(role);
	}

	protected boolean isCurrentUser(String name) {
		Identity identity = Identity.instance();
		return identity.getCredentials().getUsername().equals(name);
	}

	// ------------------------------------
	// workflow related
	// ------------------------------------

	public List<ActionMetadata> getStatusActions() {
		if (!isManaged()) {
			return Collections.EMPTY_LIST;
		}
		return getStatusMachine().getStatusActions(getCurrentStatus());
	}

	private StatusMachine statusMachine;

	protected StatusMachine getStatusMachine() {
		if (statusMachine == null) {
			statusMachine = new StatusMachine(this);
			initStatusMachine(statusMachine);
		}
		return statusMachine;
	}

	protected Object getCurrentStatus() {
		return null;
	}

	protected void initStatusMachine(StatusMachine statusMachine) {
	}

	@In(value = "tree", create = true)
	protected RootTreeNode root;

	protected RootTreeNode initRoot(EntityTreeNode<E> node) {
		treeNode = (EntityTreeNode<E>) root.init(node);
		addRecentItem();
		setViewMode(isViewMode());
		return root;
	}

	public boolean isTop() {
		if (getTreeNode() == null) {
			return false;
		}
		return getTreeNode().isTop();
	}

	private EntityTreeNode<E> treeNode;

	public EntityTreeNode<E> getTreeNode() {
		return treeNode;
	}

	public void setTreeNode(EntityTreeNode<E> treeNode) {
		this.treeNode = treeNode;
		if (treeNode != null) {
			setId(treeNode.getKey());
			setInstance(treeNode.getInstance());
		} else {
			setId(null);
			setInstance(null);
		}
	}

	public boolean isCurrentNode() {
		return getTreeNode() != null && getTreeNode().isCurrentNode();
	}

	protected boolean isMasterNode() {
		return getTreeNode() != null && getTreeNode().isMasterNode();
	}

	// ------------------------------------
	// Recent History related
	// ------------------------------------

	protected void addRecentItem() {
		if (isMasterNode() && getId() != null && isManaged()
				&& !JsfUtil.getViewId().contains("pdf")) {
			RecentHistory.instance().addRecentItem(getName(),
					getEntityClassName(), getRecentItemText(), getId());
		}
	}

	protected void removeRecentItem() {
		if (getInstance() != null) {
			RecentHistory.instance().removeRecentItem(getName(),
					JpaUtil.getIdentity(getInstance()));
		}
	}

	protected String getRecentItemText() {
		if (getTreeNode() != null) {
			return getTreeNode().getText();
		} else {
			return getId().toString();
		}
	}

	// ------------------------------------
	// Export related
	// ------------------------------------

	@Out("renderExport")
	public boolean isRenderExport() {
		return !isSimpleEntity();
	}

	public void getExportXml(OutputStream os) {
		BetwixtUtil.exportXml(getInstance(), os,
				new EmptySuppressionStrategy());
	}
}
